package com.gao.security.autoconfig.verificationcode.filter;


import com.gao.security.autoconfig.verificationcode.properties.RegisterUrlService;
import com.gao.security.verificationcode.common.VerificationCodeConstants;
import com.gao.security.verificationcode.exception.ValidateCodeIncorrectException;
import com.gao.security.verificationcode.exception.ValidateCodeNotFoundException;
import com.gao.security.verificationcode.exception.ValidateCodeTimeoutException;
import com.gao.security.verificationcode.web.verificate.VerificationCodeManager;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 验证码过滤器
 * </p>
 *
 * @author GaoShuangWen
 */
public class ValidateCodeAuthenticationFilter extends
        OncePerRequestFilter implements ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(ValidateCodeAuthenticationFilter.class);

    private List<RequestMatcher> requestMatchers = Lists.newArrayList();

    private ApplicationContext applicationContext;

    private VerificationCodeManager manager;

    private AuthenticationFailureHandler failureHandler ;

    public ValidateCodeAuthenticationFilter() {

    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!requiresAuthentication(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        String code = request.getParameter(VerificationCodeConstants.VALIDATE_CODE);
        if ( code == null) {
            log.debug("ValidateCode not found");
            ValidateCodeNotFoundException v = new ValidateCodeNotFoundException("ValidateCode not found");
            failureHandler.onAuthenticationFailure(request, response, v);
            throw v;
        }
        try {
            manager.verification(request);
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            if (e instanceof ValidateCodeTimeoutException) {
                failureHandler.onAuthenticationFailure(request, response, (ValidateCodeTimeoutException) e);
            } else if (e instanceof ValidateCodeIncorrectException) {
                failureHandler.onAuthenticationFailure(request, response, (ValidateCodeIncorrectException) e);
            } else {
                throw e;
            }
        }

    }

    private boolean requiresAuthentication(HttpServletRequest request) {
        return requestMatchers.stream()
                .anyMatch(requestMatcher -> requestMatcher.matches(request));
    }



    @Override
    public void afterPropertiesSet() throws ServletException {
        List<String> sendUrl = applicationContext.getBean(RegisterUrlService.class).getCheckUrl();
        this.requestMatchers= sendUrl.stream()
                .map(s -> new AntPathRequestMatcher(s, "POST"))
                .collect(Collectors.toList());

        this.manager= applicationContext.getBean(VerificationCodeManager.class);
        super.afterPropertiesSet();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
        this.failureHandler = failureHandler;
    }
}
